# coding: utf-8
# author: t_wolf

from initial import *
from pre_filter import *
from post_filter import *
from base_pages import *

# 定义正则表达式
_regexp_worker_scan = re.compile(
    r"\n@app_adapter\(\s*(mapping\s*=\s*[\"'](.+?)[\"'])?\s*\)\s*(?:.*?\s?)*class\s*(\w+?)\(Base\):"
)
_regexp_backer_scan = re.compile(
    r"\n@app_stalker(?:.*?\s?)*class\s*(\w+?)\(Base\):"
)
_regexp_trigger_scan = re.compile(
    r"\n@frame_trigger\(\s*(mapping\s*=\s*[\"'](.+?)[\"'])?\s*\)\s*(?:.*?\s?)*class\s*(\w+?)\(Base\):"
)
_regexp_app_enabled = re.compile(r"'enabled'\s*:\s*True\s*,")
_regexp_app_name = re.compile(r"'app_name'\s*:\s*'([^!@#$%&*()]*)'\s*,")


def _scan_app_list(fold):
    """
    应用扫描函数,扫描APP_PATH目录下的应用列表
    :param fold: 扫描目录,默认为配置文件中的app_path
    :return: 返回应用目录列表
    """
    dir_list = []
    for _dir in os.listdir(fold):
        _path = os.path.join(fold, _dir)
        if os.path.isdir(_path):
            if os.path.exists(os.path.join(_path, 'settings/configs.py')):
                _config_file = open(os.path.join(_path, 'settings/configs.py')).read()
                _name = search_in_content(_regexp_app_name, _config_file, 1)
                dir_info = [_name, _path]
                if search_in_content(_regexp_app_enabled, _config_file):
                    dir_info.append(True)
                else:
                    dir_info.append(False)
                dir_list.append(dir_info)

    return dir_list


def _generate_app_useful_inner_object(app_path):
    """
    扫描application目录,预加载app配置
    :param app_path: app所在路径
    :return: None
    """
    logger.dev("app_path: %s" % app_path)

    # 通过app_path绑定app内部对象
    temp_storage = storage()

    # 设置AppID
    temp_storage.id = app_ids.next()

    # 导入app内部对象
    app_configs = import_module_from_file_path(os.path.join(app_path, 'settings/configs.py')).APP_CONF
    app_vars = import_module_from_file_path(os.path.join(app_path, 'settings/variables.py'))
    app_sql = import_module_from_file_path(os.path.join(app_path, 'settings/sql.py'))
    app_urlmap = import_module_from_file_path(os.path.join(app_path, 'settings/urlmaps.py'))

    # 设置appName
    temp_storage.name = app_configs['APP']['app_name']

    # 设置cookie timeout
    temp_storage.cookie_timeout = app_configs['APP']['session_options']['timeout']

    # 初始化模板渲染
    app_pages = app_configs['APP']['pages_path']
    app_pages_layout = app_configs['APP']['pages_layout']
    app_pages_kwargs = {'cache': False}
    if not_empty(app_pages_layout):
        app_pages_kwargs.update({'base': app_pages_layout})

    temp_storage.pages = web.template.render(os.path.join(app_path, app_pages), **app_pages_kwargs)

    # 初始化日志对象
    app_log_file = app_configs['APP']['app_log']
    app_log_level = app_configs['APP']['log_level']
    temp_storage.logger = LOGGER(app_log_level, os.path.join(app_path, app_log_file))

    # 初始化数据库对象
    app_db_enabled = app_configs['APP']['db_enabled']
    app_db_configs = app_configs['APP']['db']
    temp_storage.db = DbUtil(app_db_configs, app_sql) if app_db_enabled else None

    # 初始化专属Redis数据库
    if app_configs['APP']['redis_enabled']:
        temp_storage.redis = DbUtil.redis_connector(
            app_configs['APP']['redis_options']['host'],
            app_configs['APP']['redis_options']['port'],
            app_configs['APP']['redis_options'].get('db', temp_storage.id)
        )
    else:
        temp_storage.redis = None

    # 初始化会话选项
    temp_storage.session_enabled = app_configs['APP']['session_enabled']
    temp_storage.session_options = app_configs['APP']['session_options']

    # 初始化全局filter
    temp_storage.filters = app_configs['APP']['filters']

    # 初始化自变量
    temp_storage.vars = storager(get_object_from_module(app_vars, dict))
    temp_storage.url_map = get_object_from_module(app_urlmap, dict)

    # 保存所有配置项
    temp_storage.configs = storager(app_configs['APP'])

    # 运行时变量装载
    web.config.apps[app_path] = temp_storage

    return None


def _structure_apps_route(fold):
    """
    扫描当前生效的所有application
    :param fold: app目录
    :return: 返回一个app映射列表
    """
    app_map = []
    for _file in os.listdir(fold):

        _path = os.path.join(fold, _file)
        _app_conf = os.path.join(_path, "settings/configs.py")

        if os.path.isdir(_path) and os.path.exists(_app_conf):

            _conf = open(_app_conf).read()
            if search_in_content(_regexp_app_enabled, _conf):
                app_name = search_in_content(_regexp_app_name, _conf, 1)
                app_map.extend(["/%s" % app_name, os.path.join(_path, 'www').replace(os.sep, os.extsep)])

                # 生成app的内部对象
                _generate_app_useful_inner_object(_path)

    return app_map


def _scan_frame_worker(fold):
    """
    扫描函数,所有用frame_worker修饰的类都被视作框架对象
    :param fold: 扫描的目录,默认为配置文件中的app_path
    :return: 返回一个扫描结果字典
    """
    dict_map = []
    for _file in os.listdir(fold):
        _path = os.path.join(fold, _file)
        if os.path.isdir(_path):
            dict_map.extend(_scan_frame_worker(_path))
        else:
            if os.path.splitext(_path)[1] == '.py' and _file not in ('__init__.py',):
                dict_map.extend([{
                    os.path.splitext(_path)[0].replace(os.path.sep, os.path.extsep): re.findall(
                        _regexp_worker_scan,
                        open(_path).read()
                    )
                }])

    return dict_map


def _scan_frame_backer(fold):
    """
        扫描函数,所有用frame_backer修饰的类都被视作框架对象
        :param fold: 扫描的目录,默认为配置文件中的app_path
        :return: 返回一个扫描结果字典
        """
    dict_map = []
    for _file in os.listdir(fold):
        _path = os.path.join(fold, _file)
        if os.path.isdir(_path):
            dict_map.extend(_scan_frame_backer(_path))
        else:
            if os.path.splitext(_path)[1] == '.py' and _file not in ('__init__.py',):
                dict_map.extend([{
                    os.path.splitext(_path)[0].replace(os.path.sep, os.path.extsep): re.findall(
                        _regexp_backer_scan,
                        open(_path).read()
                    )
                }])

    return dict_map


def _pre_inject_function_list(func_list, tag_func, bond_obj, on_fail=None, asc=False):
    """
    递归前置注入,注入顺序(默认): 右->左
    :param func_list: 注入的方法及方法参数列表
    :param tag_func: 被注入的目标方法
    :param on_fail: 过滤器返回失败时的on_call方法
    :param asc: 是否顺序
    :return:
    """
    if asc:
        func_list.reverse()

    if len(func_list) == 1:
        return lambda: tag_func() if func_list[0][0](bond_obj, func_list[0][1]) else on_fail()
    elif len(func_list) > 1:
        return lambda: _pre_inject_function_list(func_list[:-1], tag_func, bond_obj)() if \
            func_list[-1][0](bond_obj, func_list[-1][1]) else func_list[-1][2]()
    else:
        return tag_func


def _post_inject_function_list(func_list, tag_func, bond_obj, desc=False):
    """
    递归后置注入,注入顺序(默认): 左->右
    :param func_list: 注入的方法及方法参数列表
    :param tag_func: 被注入的目标方法
    :param bond_obj: 依赖的对象
    :param desc: 是否逆序
    :return:
    """
    if desc:
        func_list.reverse()

    if len(func_list) == 1:
        return lambda: func_list[0][0](bond_obj, func_list[0][1], tag_func())
    elif len(func_list) > 1:
        return lambda: func_list[-1][0](
            bond_obj, func_list[-1][1], _post_inject_function_list(func_list[:-1], tag_func, bond_obj)()
        )
    else:
        return tag_func


def _frame_caller_wrapper(method):
    """
    将复杂的注入流程进行一次封装
    :param method: 需要封装的方法
    :return: 返回封装后的方法
    """

    def wrapper_origin_func(self):
        return _pre_inject_function_list(
            self.pre_filter_list,
            _post_inject_function_list(self.post_filter_list, getattr(self, method), self),
            self,
        )()

    return wrapper_origin_func


def _frame_variable_assign(method):
    """
    为每次请求注入活力
    :param method: 要注入的请求类型
    :return: 返回一个充满新鲜变量的请求对象
    """

    def variable_inject(self, request_pattern=None):
        inject_object_by_dict(
            self,
            {
                "header": web.ctx.env,
                "cookies": web.cookies(),
                "session": web.ctx.session if self.session_enabled else {},
                "request": REQUEST.DISPOSE_DATA[REQUEST.DataType.PARAM](),
                "pattern": request_pattern,
            }
        )
        logger.debug("current request[%s: %s]" % (self.header_get("REQUEST_METHOD"), self.header_get("PATH_INFO")))
        # logger.dev(" Header: %s" % self.header)
        # logger.dev("Session: %s" % self.session.items())
        # logger.dev("Request: %s" % self.request.items())

        return getattr(self, method)()

    return variable_inject


def _generate_dynamic_url_maps(app_fold):
    """
    利用导入间隙根据配置生成URL映射表
    :param app_fold: app目录
    :return: 返回url_map
    """
    logger.dev("arguments: url_mode: [dynamic], app_fold: %s" % app_fold)

    # 动态获取URL映射,扫描业务目录的frame_worker装饰器
    tmp_map, mid_map = [], []
    tmp_map = _scan_frame_worker(app_fold)
    for _dict in tmp_map:
        for _key, _values in _dict.items():
            [mid_map.append(
                (_map[1], "%s.%s" % (_key, _map[2])) if _map[0] != '' else
                ("%s/%s" % (_key[_key.index('.'):].replace(os.path.extsep, os.path.sep), _map[2]
                            ), "%s.%s" % (_key, _map[2]))
            ) for _map in _values]

    url_map = tuple(reduce(lambda x, y: list(x) + list(y), mid_map))
    del tmp_map, mid_map

    return url_map


def _generate_static_url_maps(settings=None, app_ext_path=None):
    """
    扫描目录获取静态配置
    :param settings: app设置
    :param app_ext_path: 调用前缀
    :return: 返回url_map
    """
    logger.dev("arguments: url_mode: [default]")

    # 使用CONFIGS中的urlmaps的配置来进行URL映射关系绑定
    if settings is None:
        try:
            tmp_map = getattr(web.config.apps.single, CONFIGS.APP.url_map)
        except AttributeError:
            tmp_map = DEFAULT_MAP
        for _map in tmp_map.keys():
            tmp_map[_map] = "%s.%s" % (CONFIGS.FRAME.APP_PATH, tmp_map[_map])
    else:
        try:
            tmp_map = getattr(web.config.apps[settings.APP.app_path].url_map, settings.APP.url_map)
        except AttributeError:
            tmp_map = DEFAULT_MAP

        for _map in tmp_map.keys():
            tmp_map[_map] = "%s.%s" % (app_ext_path, tmp_map[_map])

    url_map = tuple(reduce(lambda x, y: list(x) + list(y), tmp_map.items()))

    return url_map


def _deploy_link_on_default_web():
    """
    在使用web.py自身启动web服务时,多应用下需要启用映射
    :return: None
    """
    system_remove_children(os.path.join(ROOT_PATH, 'static'), ['common', 'README'])
    for name, path, enabled in _scan_app_list(CONFIGS.FRAME.APP_PATH):
        if enabled:
            source_path = os.path.join(ROOT_PATH, path, 'resources')
            target_path = os.path.join(ROOT_PATH, 'static', name)
            if PLATFORM in ('darwin', 'linux2'):
                cmd_str = "ln -s %s %s" % (source_path, target_path)
            elif PLATFORM in ('win32', ):
                cmd_str = "mklink /J %s %s" % (target_path, source_path)
            else:
                cmd_str = ""
                write_console("This Platform not Supported.")

            system_command(cmd_str)

    return None


def _frame_init(settings):
    """
    框架初始化方法
    :param settings: 获取当前框架配置变量
    :return: 返回Url映射
    """
    # 初始化框架调试模式
    web.config.debug = CONFIGS.FRAME.DEBUG
    web.config.debug_sql = CONFIGS.FRAME.DEBUG_SQL

    # 首先判断当前启动模式,是多应用模式还是单应用模式
    web.config.apps.enabled = settings.FRAME.MULTI_APP
    if settings.FRAME.MULTI_APP:
        # 如果是多应用模式,则需要扫描应用列表,导入各个应用自身配置
        url_map = _structure_apps_route(settings.FRAME.APP_PATH)

    else:
        # 为独立应用装载变量
        temp_storage = storage()

        # 设置cookie timeout
        temp_storage.cookie_timeout = CONFIGS.APP.session_options.timeout

        # 初始化模板渲染
        app_pages = CONFIGS.APP.pages_path
        app_pages_layout = CONFIGS.APP.pages_layout
        app_pages_kwargs = {'cache': False}
        if not_empty(app_pages_layout):
            app_pages_kwargs.update({'base': app_pages_layout})

        temp_storage.pages = web.template.render(app_pages, **app_pages_kwargs)

        # 初始化日志对象
        temp_storage.logger = logger

        # 初始化数据库对象
        temp_storage.db = DbUtil(CONFIGS.APP.db, sql_sentences) if CONFIGS.APP.db_enabled else None

        # 初始化专属Redis数据库
        if CONFIGS.APP.redis_enabled:
            temp_storage.redis = DbUtil.redis_connector(
                CONFIGS.APP.redis_options.host,
                CONFIGS.APP.redis_options.port,
                CONFIGS.APP.redis_options.get('db', app_ids.next())
            )
        else:
            temp_storage.redis = None

        # 初始化会话选项
        temp_storage.session_enabled = CONFIGS.APP.session_enabled
        temp_storage.session_options = CONFIGS.APP.session_options

        # 初始化全局filter
        temp_storage.filters = CONFIGS.filters

        # 初始化自变量
        temp_storage.vars = storager(get_object_from_module(all_variable, dict))
        temp_storage.url_map = get_object_from_module(url_maps, dict)

        # 保存所有配置项
        temp_storage.configs = CONFIGS.APP

        # 运行时变量装载
        web.config.apps.single = temp_storage

        # 独立应用无需进行应用目录强前置
        if settings.APP.url_mode == 'dynamic':
            url_map = _generate_dynamic_url_maps(settings.FRAME.APP_PATH)
        elif settings.APP.url_mode == 'default':
            url_map = _generate_static_url_maps()
        else:
            url_map = []

    return url_map


def _app_init(settings):
    """
    Application初始化
    :param settings: 应用的配置对象
    :return: 返回Url映射
    """

    app_relate_path = os.path.join(settings.APP.app_path, settings.APP.class_path)

    if settings.APP.url_mode == 'dynamic':
        url_map = _generate_dynamic_url_maps(app_relate_path)
    elif settings.APP.url_mode == 'default':
        url_map = _generate_static_url_maps(settings, app_relate_path.replace(os.sep, os.extsep))
    else:
        url_map = []

    url_map = list(url_map)
    run_mode = CONFIGS.FRAME.RUN_OPTIONS.MODE
    # 映射静态文件
    if run_mode == 'DEFAULT':
        url_map.extend(["/resources/(.*)", new_redirect(settings.APP.app_name)])

    return url_map


########################################################################################################################
# 框架装饰器

def app_adapter(**kwargs):
    """
    指定类为框架工厂类,必须在业务最顶层引入
    :param kwargs: frame_worker设置参数
    :return: 注入处理数据后的类
    """

    def class_wrapper(cls):

        url_origin = kwargs.get("mapping", "/%s" % cls.__name__)
        async = kwargs.get("async", False)
        block = kwargs.get("block", False)

        curr_app = None
        if web.config.apps.enabled:
            curr_app = search_in_content(
                r'(%s/\w+?)/' %
                CONFIGS.FRAME.APP_PATH,
                get_abs_path(CALL_FILE_POSITION).replace(ROOT_PATH, "")[1:],
                1
            )
            curr_app_configs = web.config.apps[curr_app]
        else:
            curr_app_configs = web.config.apps.single

        # 注入filter
        if not hasattr(cls, 'pre_filter_list'):
            setattr(cls, 'pre_filter_list', [])
        if not hasattr(cls, 'post_filter_list'):
            setattr(cls, 'post_filter_list', [])

        cls.pre_filter_list.extend(reversed(curr_app_configs.filters['pre_filters']))
        cls.post_filter_list.extend(curr_app_configs.filters['post_filters'])

        # 根据app_path向对应的应用下属对象注入应用变量
        inject_object_by_dict(
            cls,
            {
                "app_adapter": "worker",
                "url_origin": url_origin,
                "cookie_expired": curr_app_configs.cookie_timeout,
                "pages": curr_app_configs.pages,
                "redis": curr_app_configs.redis,
                'vars': curr_app_configs.vars,
                'path': os.path.join(ROOT_PATH, curr_app) if curr_app else ROOT_PATH,
                'log': curr_app_configs.logger,
                'db': curr_app_configs.db,
                # 运行时变量
                "session_enabled": curr_app_configs.session_enabled,  # RUNTIME_PARAMS 是否启用Session
                "belong_app": curr_app,  # RUNTIME_PARAMS 标记归属App
            }
        )

        for _method in REQUEST.Method.method_list:
            setattr(cls, 'deposit_%s' % _method, _frame_caller_wrapper(_method))
            setattr(cls, _method.upper(), _frame_variable_assign('deposit_%s' % _method))

        return cls

    return class_wrapper


def app_stalker(**kwargs):
    """
    设置框架定时任务
    :param kwargs: 定时任务参数
    :return: 返回定时任务
    """
    s = kwargs.get("s", 10)
    ms = kwargs.get("ms", 0)
    cron = kwargs.get("cron", None)          # "*/60 * * * *"
    counts = kwargs.get("counts", None)      # 执行次数

    def time_worker(cls):
        curr_app = None
        if web.config.apps.enabled:
            curr_app = search_in_content(
                r'(%s/\w+?)/' %
                CONFIGS.FRAME.APP_PATH,
                get_abs_path(CALL_FILE_POSITION).replace(ROOT_PATH, "")[1:],
                1
            )
            curr_app_configs = web.config.apps[curr_app]
        else:
            curr_app_configs = web.config.apps.single

        # 根据app_path向对应的应用下属对象注入定时器
        inject_object_by_dict(
            cls,
            {
                "app_adapter": "backer",
                "cookie_expired": curr_app_configs.cookie_timeout,
                "pages": curr_app_configs.pages,
                "redis": curr_app_configs.redis,
                'vars': curr_app_configs.vars,
                'path': os.path.join(ROOT_PATH, curr_app) if curr_app else ROOT_PATH,
                'log': curr_app_configs.logger,
                'db': curr_app_configs.db,
                # 运行时变量
                "belong_app": curr_app,  # RUNTIME_PARAMS 标记归属App
                # 定时任务变量
                "s": s,
                "ms": ms,
                "cron": cron,
                "counts": counts,
            }
        )

        return cls

    return time_worker


def reg_pre_filter(filter_item, arg_list=None, on_fail=None):
    """
    注册过滤器,插入对象前就是全局过滤,插入方法前就是局部过滤,只针对特定请求
    :param filter_item: 过滤方法
    :param arg_list: 过滤方法的入参列表
    :param on_fail: 过滤失败如何处理
    :return: 过滤通过则接着往下走,否则返回异常
    """

    def inject_request(obj):
        if hasattr(obj, "func_doc"):
            return lambda self: obj(self) if filter_item(self, arg_list) else on_fail()
            # return _pre_inject_function_list([(filter_item, arg_list), ], obj, obj.__class__, on_fail)
            # def request_injector(self):
            #     return obj(self) if filter_item(self, tuple(arg_list)) else on_fail
            #
            # return request_injector
        else:
            if not hasattr(obj, 'pre_filter_list'):
                setattr(obj, 'pre_filter_list', [])
            obj.pre_filter_list.append([filter_item, arg_list, on_fail])
            return obj

    return inject_request


def reg_post_filter(filter_item, arg_list=None):
    """
    后置处理器,对Response进行处理,同filter,插入对象前为全局,方法前为局部
    :param filter_item: 处理方法
    :param arg_list: 方法入参
    :return: 返回处理后的Response
    """

    def inject_response(obj):
        if hasattr(obj, "func_doc"):
            return lambda self: filter_item(self, arg_list, obj(self))
            # return _post_inject_function_list([(filter_item, arg_list), ], obj)
            # def request_injector(self):
            #     return filter_item(self, obj(self), tuple(arg_list))
            #
            # return request_injector
        else:
            if not hasattr(obj, 'post_filter_list'):
                setattr(obj, 'post_filter_list', [])
            obj.post_filter_list.append([filter_item, arg_list])
            return obj

    return inject_response


########################################################################################################################


def to_children_app(app_map):
    """
    将Url映射String转换为实际的导入App对象
    :param app_map: App映射表
    :return: 返回实例化后的App映射
    """
    app_list = []

    if web.config.apps.enabled:
        for i in xrange(len(app_map)):
            if i % 2:
                www = importlib.import_module(app_map[i])
                app_list.append(www.app)
            else:
                app_list.append(app_map[i])

    else:
        app_list = list(app_map)

    # 映射静态文件
    if CONFIGS.FRAME.RUN_OPTIONS.MODE == 'DEFAULT':
        _deploy_link_on_default_web()

    return app_list


def add_base_optional(url_map):
    url_map.extend([
        '/?', EasyWebEntry,
        '/expired', ExpiredEasyWeb,
    ])
    return url_map


def entrance_init(settings):
    """
    框架主入口,根据配置变量中的FRAME对象来判断是否为框架初始化
    :param settings: 配置变量
    :return: 返回url_map
    """
    # 框架主入口
    if hasattr(settings, 'FRAME'):
        return _frame_init(settings)
    else:
        return _app_init(settings)


def scan_back_worker(fold):
    """
    扫描指定目录下的back_worker
    :param fold: 目录绝对路径
    :return: 返回对象字符串列表
    """
    backer_list = []
    for py_container in _scan_frame_backer(fold):
        [backer_list.append(
            {"module": _key, "class": _value}
        ) for _key, _value in py_container.items() if _value != []]

    return backer_list


########################################################################################################################


# 导出方法供外部使用
scan_app_list = _scan_app_list
